#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

const int gcap = 5; // 默认容量

// 不要认为，阻塞队列只能放整数字符串之类的
// 也可以放对象
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int cap = gcap)
        : _cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr /*属性*/);
        pthread_cond_init(&_consumerCond, nullptr);
        pthread_cond_init(&_productorCond, nullptr);
    }

    bool isFull()
    {
        return _q.size() == _cap;
    }

    bool isEmpty()
    {
        return _q.empty();
    }

    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        // 细节1：一定要保证，在任何时候，都是符合条件，才进行生产，比如只消费一个，而全部生产都被唤醒，被误唤醒，所以用while
        while (isFull()) // 1、我们只能在临界区内部，判断临界资源是否就绪,注定了我们在当前一定是持有锁的
                         // 因为临界资源本身是被多线程共享的，
                         // 所以任何访问修改临界资源对应的操作都可能会访问临界资源，所以这个判断一定是在加锁解锁之间的
        {
            // 如果队列是满的，生产就等待休眠
            // 2、要让线程进行休眠等待，不能持有锁等待
            // 3、就注定了，pthread_cond_wait要有锁的释放能力
            pthread_cond_wait(&_productorCond, &_mutex); // 我休眠（本质是切换线程）了，我醒来的时候，在哪里往后执行呢
            // 4、当线程醒来的时候，注定了继续从临界区内部继续运行，因为我是在临界区就被切走的
            // 5、注定了，当线程被唤醒的时候，继续在pthread_cond_wait函数处向后运行,又要重新申请锁，申请成功才会彻底返回
        }
        // 没有满的，就要让他进行生产
        _q.push(in);
        // 也可以加策略，什么情况唤醒消费者
        // if(_q.size()>=_cap/2)//容量大于一半了，让消费者来消费,今天不加策略
        pthread_cond_signal(&_consumerCond);
        pthread_mutex_unlock(&_mutex);
    }

    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);

        while (isEmpty()) // 不能用if，要用while循环，去判读是否为空，生产同理
        {
            pthread_cond_wait(&_consumerCond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        // 加策略,唤醒生产者
        pthread_cond_signal(&_productorCond); // 可以放在解锁前和解锁后，都是一样的
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumerCond);
        pthread_cond_destroy(&_productorCond);
    }

private:
    std::queue<T> _q; // 阻塞队列

    int _cap; // 这个队列的容量
    // 这个队列是一个临界资源，被同时多个线程访问，要保证他们安全
    pthread_mutex_t _mutex;//为什么我们这份代码，只用一把锁呢？根本原因在于，我们生产和消费访问的是同一个queue&&queue被当成整体使用

    // 如果造成饥问题，频繁检测，生产刚生产知道有数据，消费刚消费知道有数据，生产和消费可以现场互相通知，来保证他们的同步
    // pthread_cond_t _cond;

    pthread_cond_t _consumerCond;  // 消费者对应的条件变量，是空，就wait
    pthread_cond_t _productorCond; // 生产者对应的条件变量，是满，就wait
};